package org.nViron.nvProjection;

//~--- non-JDK imports --------------------------------------------------------

import org.nViron.nvCommon.nvPosition;
import org.nViron.nvRender.nvVec3;

/**
 * Implementation of mercator projection
 * @author Roland Haring <p>
 * <script type="text/javascript" src="http://www.ohloh.net/p/376434/widgets/project_basic_stats.js"></script>
 */
public class nvMercator implements nvProjection {

    // TODO find better scaling to avoid floating point problems
    // scale both to the same reference metric:
    protected float altitudeScale = 0.0005f;    // internal scaling of altitude from meters to kilometers and then applying a good looking factor
    protected float geoScale = 40075.1600f;    // 40075160m is the circumference of earth at equator -> scaling from meters to kilometers
    protected float   offset[] = new float[3];    // x, y and z offset
    protected boolean isCenterSet;

    public nvMercator() {
        isCenterSet = false;
    }

    public nvVec3 geoToViz(nvPosition pos) {
        nvVec3 ret = new nvVec3();

        ret.x = ((float) pos.getLongitude() + 180.0f) / 360.0f * geoScale + offset[0];
        ret.y = pos.getAltitude() * altitudeScale + offset[1];
        ret.z = (1.0f
                 - (float) (Math.log(Math.tan(pos.getLatitude() * Math.PI / 180.0)
                                     + 1.0f / Math.cos(pos.getLatitude() * Math.PI / 180.0)) / Math.PI)) / 2.0f
                                         * geoScale + offset[2];

        return ret;
    }

    public float geoToVizApprox(float meters)    // can be wrong up to a factor of 2 approx.
    {
        return meters * geoScale / 40075160.0f;
    }

    public float getAltitudeScale() {
        return altitudeScale;
    }

    public float getGeoScale() {
        return geoScale;
    }

    public void setAltitudeScale(float altitudeScale) {
        this.altitudeScale = altitudeScale;
    }

    public void setCameraPos(nvPosition pos) {

        // TODO find a better way to update the center offset
        // problem: aabb of maptiles store viz coordinates permanently. if center is moved all the time they become obsolete. updating them every time is also a extreme overhead
        // so find a way to keep a center as long as possible, but if it gets too far away then you need to the a update and reset all the cache data (use a listener?)!
        if (!isCenterSet) {
            nvVec3 vec = geoToViz(pos);

            offset[0] -= vec.x;

            // offset[1] -= vec.y; // does not work for y axis somehow (but we can just skip it)
            offset[2]   -= vec.z;
            isCenterSet = true;
        }
    }

    public void setGeoScale(float geoScale) {
        this.geoScale = geoScale;
    }

    public nvPosition vizToGeo(nvVec3 vec) {
        nvPosition ret = new nvPosition();

        ret.setLongitude((vec.x - offset[0]) / geoScale * 360.0 - 180.0);

        double n = Math.PI - (2.0 * Math.PI * ((vec.z - offset[2]) / geoScale));

        ret.setLatitude(180.0 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))));
        ret.setAltitude((vec.y - offset[1]) / altitudeScale);

        return ret;
    }
}
